#include <nlohmann/json.hpp>
#include <spdlog/spdlog.h>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>

#include "action.hpp"
#include "factory.hpp"

/**
 * example json in rule:
    {
        "type": "program",
        "name": "test_program_5_second",
        "path": "/home/orangepi/dev/reconfigurable-selfbalance-robot/build/daemon",
        "program": "example_program",
        "args": []
    }
 */

class Program : public Action
{
  private:
    std::string path;
    std::string program;
    std::string args;
    pid_t pid = -1;

  public:
    Program(const nlohmann::json &j) : Action(j)
    {
        j.at("path").get_to(path);
        j.at("program").get_to(program);
        if (!j.contains("args"))
        {
            args.clear();
            return;
        }
        auto argList = j.at("args").get<std::vector<std::string>>();
        for (auto argp : argList)
        {
            args += argp + " ";
        }
    }

    virtual bool Execute() override final
    {
        // check program is exist
        pid = fork();
        if (pid == 0)
        {
            // child process
            execl(path.c_str(), program.c_str(), args.c_str(), (char *)0);
            // if execl fails, the program will terminate
            exit(1);
        }
        else if (pid < 0)
        {
            // fork failed
            spdlog::error("Creat subprocess failed");
            return false;
        }
        else
        {
            // parent process
            spdlog::info("child process executing on pid: {}", pid);
            int status;
            waitpid(pid, &status, 0);
            if (WIFEXITED(status))
            {
                spdlog::info("Program action completed with status {}", WEXITSTATUS(status));
                return true;
            }
            else
            {
                if (WIFSIGNALED(status))
                {
                    spdlog::warn("Program action terminated with signal {}({})", strsignal(WTERMSIG(status)),
                                 WTERMSIG(status));
                    return true;
                }
                spdlog::error("Program action terminated with status {}", WEXITSTATUS(status));
                return false;
            }
        }
    }

    virtual void Terminate() final
    {
        if (pid > 0)
        {
            kill(pid, SIGTERM);
            std::this_thread::sleep_for(duration);
            int status;
            waitpid(pid, &status, WNOHANG);
            if (status == 0)
            {
                kill(pid, SIGKILL);
                spdlog::info("Time out, force terminating program action <{}>:{}", type, name);
            }
        }
    }
};

static Factory<Action>::Register<Program> reg("program");